Skip to content

refactor(webhooks): extract provider-specific logic into handler registry#3973

Open
waleedlatif1 wants to merge 18 commits intostagingfrom
waleedlatif1/refactor-panel-code
Open

refactor(webhooks): extract provider-specific logic into handler registry#3973
waleedlatif1 wants to merge 18 commits intostagingfrom
waleedlatif1/refactor-panel-code

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Extract 14+ provider-specific if-chains from processor.ts and webhook-execution.ts into a provider handler registry pattern
  • Each provider gets its own handler file in lib/webhooks/providers/ implementing only the methods it needs
  • Shared utilities: createHmacVerifier factory, verifyTokenAuth helper, skipByEventTypes filter
  • processor.ts reduced from 1468 → ~685 lines, webhook-execution.ts from 713 → ~570 lines
  • Removes verifyProviderWebhook from utils.server.ts (merged into per-provider auth)
  • Deletes provider-utils.ts (folded into registry)
  • 3 security improvements: timing-safe token comparison for generic, google-forms, and default handler
  • 1 bug fix: Jira event matching now correctly passes issueEventTypeName as string
  • Updates add-trigger skill with webhook provider handler documentation

Type of Change

  • Refactoring (no user-facing behavior changes)

Testing

  • All 24 webhook tests passing
  • Zero TypeScript errors
  • Validated via 4 parallel regression agents tracing every provider's auth, event matching, idempotency, responses, and execution lifecycle line-by-line against original code

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@cursor
Copy link
Copy Markdown

cursor bot commented Apr 5, 2026

PR Summary

High Risk
Centralizes webhook auth, event filtering, subscription lifecycle, polling setup, and response formatting behind a new provider handler registry, touching the core webhook ingestion/execution path. While mostly a refactor, regressions could impact signature verification, idempotency, and external subscription cleanup across many providers.

Overview
Refactors the webhook system to route all provider-specific behavior (auth/signature checks, challenge handling, reachability tests, event matching/skip logic, idempotency ID extraction, input formatting, file processing, and custom success/error responses) through a getProviderHandler() registry rather than large provider if/else chains.

Moves external subscription lifecycle management into handler methods (createSubscription/deleteSubscription) and updates webhook creation/deploy flows to call optional handler hooks like configurePolling, removing provider-specific orchestration code from route.ts, deploy.ts, and massively simplifying provider-subscriptions.ts.

Updates docs (.claude/commands/add-trigger.md) to instruct adding provider handlers instead of editing orchestration files, tightens types (unknown/Record<string, unknown>), and removes legacy helpers like provider-utils.ts in favor of handler-based idempotency extraction.

Reviewed by Cursor Bugbot for commit 220aa91. Configure here.

@gitguardian
Copy link
Copy Markdown

gitguardian bot commented Apr 5, 2026

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Apr 6, 2026 6:39am

Request Review

@waleedlatif1 waleedlatif1 force-pushed the waleedlatif1/refactor-panel-code branch from f580455 to 6e95981 Compare April 5, 2026 03:35
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 5, 2026

Greptile Summary

This PR refactors the webhook processing pipeline by extracting 14+ provider-specific if-chains into a clean handler registry pattern. Each of the 30 providers now lives in its own file under lib/webhooks/providers/, implementing only the WebhookProviderHandler interface methods it needs. Shared utilities (createHmacVerifier, verifyTokenAuth, skipByEventTypes) reduce duplication across providers, and processor.ts is cut from ~1468 to ~685 lines.

  • Three timing-safe token comparisons added (generic, google-forms, default handler)
  • Jira issueEventTypeName bug fix: now correctly passed as string | undefined to isJiraEventMatch
  • processTriggerFileOutputs array-aware initializer restored in webhook-execution.ts
  • Duplicate generic file-processing block removed
  • verifyProviderWebhook merged into per-provider auth handlers
  • Minor (P2): unused errorBody in Teams deleteSubscription silently discards Graph API error details
  • Minor (P2): non-TSDoc inline comments in airtable.ts violate the project style guide

Confidence Score: 5/5

This PR is safe to merge; all prior P0/P1 issues have been resolved and the remaining findings are P2 style items

All previous review concerns (any types in three helpers, processTriggerFileOutputs array initializer, duplicate generic file processing, and generic requireAuth behavior) have been addressed. Remaining findings are an unused variable in logging and inline comment style violations — neither affects runtime correctness or security.

apps/sim/lib/webhooks/providers/microsoft-teams.ts (unused errorBody in deleteSubscription) and apps/sim/lib/webhooks/providers/airtable.ts (non-TSDoc comments)

Important Files Changed

Filename Overview
apps/sim/lib/webhooks/providers/registry.ts Introduces the central handler registry; clean delegation pattern with a safe timing-safe default bearer-token handler
apps/sim/lib/webhooks/providers/types.ts Well-typed strategy interface; all 15 optional methods have proper context types and TSDoc
apps/sim/lib/webhooks/providers/utils.ts Shared HMAC factory, timing-safe token verifier, and event-type filter utilities cleanly reused across providers
apps/sim/lib/webhooks/processor.ts Reduced from ~1468 to ~685 lines; helper functions now delegate to the registry; pre-existing any types in DB results unchanged
apps/sim/background/webhook-execution.ts Array-aware processTriggerFileOutputs initializer restored; delegation to handler.formatInput and handler.processInputFiles is clean
apps/sim/lib/webhooks/providers/microsoft-teams.ts Unused errorBody variable in deleteSubscription silently discards Graph API error details
apps/sim/lib/webhooks/providers/airtable.ts Complex Airtable payload-pagination logic correctly extracted; non-TSDoc inline comments violate style guide
apps/sim/lib/webhooks/providers/generic.ts Token auth, IP allowlist, idempotency field, custom response mode, and file processing all correctly ported with timing-safe comparison
apps/sim/lib/webhooks/providers/slack.ts File download, reaction-event handling, and url_verification challenge detection cleanly extracted
apps/sim/lib/webhooks/providers/jira.ts HMAC verifier wired via createHmacVerifier; issueEventTypeName bug fix correctly passes string

Sequence Diagram

sequenceDiagram
    participant Client as External Client
    participant Route as route.ts
    participant Processor as processor.ts
    participant Registry as registry.ts
    participant Handler as Provider Handler
    participant Execution as webhook-execution.ts

    Client->>Route: POST /api/webhooks/trigger/{path}
    Route->>Processor: handleProviderChallenges(body, request)
    Processor->>Registry: getProviderHandler(provider)
    Registry-->>Processor: handler
    Processor->>Handler: handler.handleChallenge(body, request)
    Handler-->>Processor: NextResponse | null

    Route->>Processor: findAllWebhooksForPath({path})
    Processor-->>Route: webhooksForPath[]

    loop For each webhook
        Route->>Processor: verifyProviderAuth(webhook, workflow, request)
        Processor->>Registry: getProviderHandler(provider)
        Registry-->>Processor: handler
        Processor->>Handler: handler.verifyAuth(ctx)
        Handler-->>Processor: NextResponse(401) | null
        Route->>Processor: shouldSkipWebhookEvent(webhook, body)
        Processor->>Handler: handler.shouldSkipEvent(ctx)
        Handler-->>Processor: boolean
        Route->>Processor: queueWebhookExecution(webhook, workflow, body)
        Processor->>Handler: handler.matchEvent(ctx)
        Handler-->>Processor: true | false | NextResponse
        Processor->>Handler: handler.enrichHeaders(ctx, headers)
        Processor->>Handler: handler.formatSuccessResponse(providerConfig)
        Processor-->>Route: NextResponse(200)
    end

    Route-->>Client: 200 Webhook processed

    Note over Execution: Async job execution
    Execution->>Registry: getProviderHandler(provider)
    Registry-->>Execution: handler
    Execution->>Handler: handler.formatInput(ctx)
    Handler-->>Execution: FormatInputResult
    Execution->>Handler: handler.processInputFiles(ctx)
    Handler-->>Execution: void
    Execution->>Execution: executeWorkflowCore(snapshot)
Loading

Reviews (7): Last reviewed commit: "refactor(webhooks): standardize logger n..." | Re-trigger Greptile

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6e95981. Configure here.

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0d116fa. Configure here.

- Restore original fall-through behavior for generic requireAuth with no token
- Replace `any` params with proper types in processor helper functions
- Restore array-aware initializer in processTriggerFileOutputs
…ggerFileOutputs

Cast array initializer to Record<string, unknown> to allow string indexing
while preserving array runtime semantics for the return value.
…gured

If a user explicitly sets requireAuth: true, they expect auth to be enforced.
Returning 401 when no token is configured is the correct behavior — this is
an intentional improvement over the original code which silently allowed
unauthenticated access in this case.
waleedlatif1 and others added 6 commits April 5, 2026 11:39
…e handler

Co-locate the ~400-line Airtable payload processing function with its
provider handler. Remove AirtableChange interface from utils.server.ts.
…fig.ts

Move configureGmailPolling, configureOutlookPolling, configureRssPolling,
and configureImapPolling out of utils.server.ts into a dedicated module.
Update imports in deploy.ts and webhooks/route.ts.
…rmatInput methods

Move all provider-specific input formatting from the monolithic formatWebhookInput
switch statement into each provider's handler file. Delete formatWebhookInput and
all its helper functions (fetchWithDNSPinning, formatTeamsGraphNotification, Slack
file helpers, convertSquareBracketsToTwiML) from utils.server.ts. Create new handler
files for gmail, outlook, rss, imap, and calendly providers. Update webhook-execution.ts
to use handler.formatInput as the primary path with raw body passthrough as fallback.

utils.server.ts reduced from ~1600 lines to ~370 lines containing only credential-sync
functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…istry pattern

Move all provider-specific subscription create/delete logic from the monolithic
provider-subscriptions.ts into individual provider handler files via new
createSubscription/deleteSubscription methods on WebhookProviderHandler.

Replace the two massive if-else dispatch chains (11 branches each) with simple
registry lookups via getProviderHandler(). provider-subscriptions.ts reduced
from 2,337 lines to 128 lines (orchestration only).

Also migrate polling configuration (gmail, outlook, rss, imap) into provider
handlers via configurePolling() method, and challenge/verification handling
(slack, whatsapp, teams) via handleChallenge() method. Delete polling-config.ts.

Create new handler files for fathom and lemlist providers. Extract shared
subscription utilities into subscription-utils.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rcation comments

- Cast `body` to `Record<string, unknown>` in attio formatInput to fix
  type error with extractor functions
- Restore `rejectUnauthorized` field in imap configurePolling for parity
- Remove `// ---` section demarcation comments from route.ts and airtable.ts
- Update add-trigger skill to reflect handler-based architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

…execution

The generic provider's processInputFiles handler already handles file[] field
processing via the handler.processInputFiles call. The hardcoded block from
staging was incorrectly preserved during rebase, causing double processing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 60610b7. Configure here.

… at deploy time

Rejects deployment with a clear error message if a generic webhook trigger
has requireAuth enabled but no authentication token configured, rather than
letting requests fail with 401 at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…olling config

The refactored IMAP handler added a rejectUnauthorized field that was not
present in the original configureImapPolling function. This would default
to true for all existing IMAP webhooks, potentially breaking connections
to servers with self-signed certificates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… handler

Per project coding standards, use generateId() from @/lib/core/utils/uuid
instead of crypto.randomUUID() directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…m providers

- Standardize logger names to WebhookProvider:X pattern across 6 providers
  (fathom, gmail, imap, lemlist, outlook, rss)
- Replace all `any` types in airtable handler with proper types:
  - Add AirtableTableChanges interface for API response typing
  - Change function params from `any` to `Record<string, unknown>`
  - Change AirtableChange fields from Record<string, any> to Record<string, unknown>
  - Change all catch blocks from `error: any` to `error: unknown`
  - Change input object from `any` to `Record<string, unknown>`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 220aa91. Configure here.

status: 400,
},
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy-time auth check applies to all providers

Medium Severity

A new deploy-time validation checks for requireAuth: true without a token across all trigger blocks. This check was intended for the generic webhook provider, where requireAuth is a specific concept. Workflows with a generic trigger that previously deployed successfully (failing at runtime) now fail at deploy time with a 400 error. This also risks incorrectly blocking future providers.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 220aa91. Configure here.

Replace 3 `catch (error: any)` with `catch (error: unknown)` and
1 `Record<string, any>` with `Record<string, unknown>`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant